
#include "stm32f4xx_hal.h"
#include "touch.h"
#include "timer.h"
#include "lcd.h"

typedef struct {
    uint8_t cmd_rdx;
    uint8_t cmd_rdy;
    float xfactor;
    float yfactor;
    int16_t xoffset;
    int16_t yoffset;
} touch_info_t;

static touch_info_t touch_dev;

static void delay_us(uint32_t us)
{
    HwMicroTimerDelayUs(us);
}

#define CS_PORT     GPIOB
#define CS_PIN      GPIO_PIN_0

#define SCK_PORT    GPIOA
#define SCK_PIN     GPIO_PIN_5

#define DIN_PORT    GPIOB
#define DIN_PIN     GPIO_PIN_2

#define DOUT_PORT   GPIOB
#define DOUT_PIN    GPIO_PIN_1

#define PEN_PORT    GPIOF
#define PEN_PIN     GPIO_PIN_11

int TouchInit(void)
{
    GPIO_InitTypeDef GPIO_InitStruct;
    GPIO_InitStruct.Pin = CS_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = 0;
    HAL_GPIO_Init(CS_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = SCK_PIN;
    HAL_GPIO_Init(SCK_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = DOUT_PIN;
    HAL_GPIO_Init(DOUT_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = PEN_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    GPIO_InitStruct.Alternate = 0;
    HAL_GPIO_Init(PEN_PORT, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = DIN_PIN;
    HAL_GPIO_Init(DIN_PORT, &GPIO_InitStruct);

    lcd_info_t lcdinfo;
    LCD_GetInfo(&lcdinfo);
    switch (lcdinfo.dir) {
        case 0:
            touch_dev.cmd_rdx = 0xD0;
            touch_dev.cmd_rdy = 0x90;
            touch_dev.xfactor = 0.06671114f;
            touch_dev.yfactor = 0.09117551f;
            touch_dev.xoffset = -11;
            touch_dev.yoffset = -18;
            break;
        case 1: // rotation 90'
            touch_dev.cmd_rdx = 0x90;
            touch_dev.cmd_rdy = 0xD0;
            touch_dev.xfactor = 0.09195402f;
            touch_dev.yfactor = -0.06736275f;
            touch_dev.xoffset = -28;
            touch_dev.yoffset = 240 + 19;
            break;
        case 2: // rotation 180'
            touch_dev.cmd_rdx = 0xD0;
            touch_dev.cmd_rdy = 0x90;
            touch_dev.xfactor = 0.06671114f;
            touch_dev.yfactor = 0.09117551f;
            touch_dev.xoffset = -11;
            touch_dev.yoffset = -18;
            break;
        case 3: // rotation 270'
            touch_dev.cmd_rdx = 0x90;
            touch_dev.cmd_rdy = 0xD0;
            touch_dev.xfactor = -0.09195402f;
            touch_dev.yfactor = 0.06736275f;
            touch_dev.xoffset = 320 + 28;
            touch_dev.yoffset = -19;
            break;
        default:
            touch_dev.cmd_rdx = 0xD0;
            touch_dev.cmd_rdy = 0x90;
            touch_dev.xfactor = 0.06671114f;
            touch_dev.yfactor = 0.09117551f;
            touch_dev.xoffset = -11;
            touch_dev.yoffset = -18;
            break;
    }

    return 0;
}

static void SPI_Write_Byte(uint8_t num)    
{
    uint8_t count = 0;
    for(count = 0; count < 8; count ++)
    {
        if(num & 0x80) {
            HAL_GPIO_WritePin(DOUT_PORT, DOUT_PIN, GPIO_PIN_SET);
        } else {
            HAL_GPIO_WritePin(DOUT_PORT, DOUT_PIN, GPIO_PIN_RESET);
        }
        num <<= 1;
        HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_RESET);
        delay_us(1);
        HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_SET);
        delay_us(1);
    }
}

static uint16_t SPI_Read_AD(uint8_t cmd)
{
    HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(DOUT_PORT, DOUT_PIN, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET);
    SPI_Write_Byte(cmd);
    delay_us(5);
    HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_RESET);
    delay_us(1);
    HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_SET);
    delay_us(1);
    HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_RESET);

    uint16_t num = 0;
    for (uint8_t count = 0; count < 16; count ++)
    {
        num <<= 1;
        HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_RESET);
        delay_us(1);
        HAL_GPIO_WritePin(SCK_PORT, SCK_PIN, GPIO_PIN_SET);
        delay_us(1);
        if(HAL_GPIO_ReadPin(DIN_PORT, DIN_PIN)) {
            num ++;
        }
    }
    num >>= 4;
    HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET);
    return num;
}

static uint16_t Read_XorY(uint8_t cmd)
{
    #define TOUCH_READ_TIMES 8

    uint16_t buf[TOUCH_READ_TIMES], max = 0, min = 0;
    uint32_t sum = 0;

    buf[0] = SPI_Read_AD(cmd);
    sum = max = min = buf[0];
    for (int i = 1; i < TOUCH_READ_TIMES; i ++) {
        buf[i] = SPI_Read_AD(cmd);
        if (buf[i] < min) min = buf[i];
        if (buf[i] > max) max = buf[i];
        sum += buf[i];
    }
    sum -= max;
    sum -= min;
    return sum / (TOUCH_READ_TIMES - 2);
}

int TouchIsPressed(void)
{
    return (HAL_GPIO_ReadPin(PEN_PORT, PEN_PIN) == GPIO_PIN_RESET);
}

/*
* @brief    TouchGetXY
* @param    mode:   0: screen coordinates;  1: real coordinates; 
* @retval   0:      get coordinates successful
*           < 0:    get coordinates failed
*/
int TouchGetXY(uint8_t mode, uint16_t *x, uint16_t *y)
{
    if (x == NULL || y == NULL) {
        return -1;
    }

    uint16_t xtemp, ytemp;
    xtemp = Read_XorY(touch_dev.cmd_rdx);
    ytemp = Read_XorY(touch_dev.cmd_rdy);
    if (xtemp < 100 || xtemp > 3900 || ytemp < 100 || ytemp > 3900) {
        return -2;
    }

    if (mode == 0) { // screen coordinates
        *x = (uint16_t)((float)xtemp * touch_dev.xfactor + (float)touch_dev.xoffset);
        *y = (uint16_t)((float)ytemp * touch_dev.yfactor + (float)touch_dev.yoffset);
        lcd_info_t lcdinfo;
        LCD_GetInfo(&lcdinfo);
        if (*x >= lcdinfo.width) *x = lcdinfo.width - 1;
        if (*y >= lcdinfo.height) *y = lcdinfo.height - 1;
    } else {    // real coordinates
        *x = xtemp;
        *y = ytemp;
    }

    return 0;
}
